{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "87684b48-150e-4e15-b0a5-a9dd7851f8fb",
   "metadata": {},
   "source": [
    "# How to build a multi-agent network (functional API)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "2c65639c-9705-49f1-840a-370718852e98",
   "metadata": {},
   "source": [
    "!!! info \"Prerequisites\" \n",
    "    This guide assumes familiarity with the following:\n",
    "\n",
    "    - [Multi-agent systems](../../concepts/multi_agent)\n",
    "    - [Functional API](../../concepts/functional_api)\n",
    "    - [Command](../../concepts/low_level/#command)\n",
    "    - [LangGraph Glossary](../../concepts/low_level/)\n",
    "\n",
    "In this how-to guide we will demonstrate how to implement a [multi-agent network](../../concepts/multi_agent#network) architecture where each agent can communicate with every other agent (many-to-many connections) and can decide which agent to call next. We will be using [functional API](../../concepts/functional_api) — individual agents will be defined as tasks and the agent handoffs will be defined in the main [entrypoint()][langgraph.func.entrypoint]:\n",
    "\n",
    "```python\n",
    "from langgraph.func import entrypoint\n",
    "from langgraph.prebuilt import create_react_agent\n",
    "from langchain_core.tools import tool\n",
    "\n",
    "\n",
    "# Define a tool to signal intent to hand off to a different agent\n",
    "@tool(return_direct=True)\n",
    "def transfer_to_hotel_advisor():\n",
    "    \"\"\"Ask hotel advisor agent for help.\"\"\"\n",
    "    return \"Successfully transferred to hotel advisor\"\n",
    "\n",
    "\n",
    "# define an agent\n",
    "travel_advisor_tools = [transfer_to_hotel_advisor, ...]\n",
    "travel_advisor = create_react_agent(model, travel_advisor_tools)\n",
    "\n",
    "\n",
    "# define a task that calls an agent\n",
    "@task\n",
    "def call_travel_advisor(messages):\n",
    "    response = travel_advisor.invoke({\"messages\": messages})\n",
    "    return response[\"messages\"]\n",
    "\n",
    "\n",
    "# define the multi-agent network workflow\n",
    "@entrypoint()\n",
    "def workflow(messages):\n",
    "    call_active_agent = call_travel_advisor\n",
    "    while True:\n",
    "        agent_messages = call_active_agent(messages).result()\n",
    "        messages = messages + agent_messages\n",
    "        call_active_agent = get_next_agent(messages)\n",
    "    return messages\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "faaa4444-cd06-4813-b9ca-c9700fe12cb7",
   "metadata": {},
   "source": [
    "## Setup\n",
    "\n",
    "First, let's install the required packages"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "05038da0-31df-4066-a1a4-c4ccb5db4d3a",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install -U langgraph langchain-anthropic"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "0bcff5d4-130e-426d-9285-40d0f72c7cd3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ANTHROPIC_API_KEY:  ········\n"
     ]
    }
   ],
   "source": [
    "import getpass\n",
    "import os\n",
    "\n",
    "\n",
    "def _set_env(var: str):\n",
    "    if not os.environ.get(var):\n",
    "        os.environ[var] = getpass.getpass(f\"{var}: \")\n",
    "\n",
    "\n",
    "_set_env(\"ANTHROPIC_API_KEY\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c3ec6e48-85dc-4905-ba50-985e5d4788e6",
   "metadata": {},
   "source": [
    "<div class=\"admonition tip\">\n",
    "    <p class=\"admonition-title\">Set up <a href=\"https://smith.langchain.com\">LangSmith</a> for LangGraph development</p>\n",
    "    <p style=\"padding-top: 5px;\">\n",
    "        Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
    "    </p>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4a53f304-3709-4df7-8714-1ca61e615743",
   "metadata": {},
   "source": [
    "## Travel agent example"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "34cd131b-f0c2-4b69-887f-2cbd5afb14a7",
   "metadata": {},
   "source": [
    "In this example we will build a team of travel assistant agents that can communicate with each other.\n",
    "\n",
    "We will create 2 agents:\n",
    "\n",
    "* `travel_advisor`: can help with travel destination recommendations. Can ask `hotel_advisor` for help.\n",
    "* `hotel_advisor`: can help with hotel recommendations. Can ask `travel_advisor` for help.\n",
    "\n",
    "This is a fully-connected network - every agent can talk to any other agent. "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fedc9ed0-e90c-4ee1-a7c6-f5af3c634a7b",
   "metadata": {},
   "source": [
    "First, let's create some of the tools that the agents will be using:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "7e31f258-ec28-4020-b86d-c91dfa9a3bfc",
   "metadata": {},
   "outputs": [],
   "source": [
    "import random\n",
    "from typing_extensions import Literal\n",
    "from langchain_core.tools import tool\n",
    "\n",
    "\n",
    "@tool\n",
    "def get_travel_recommendations():\n",
    "    \"\"\"Get recommendation for travel destinations\"\"\"\n",
    "    return random.choice([\"aruba\", \"turks and caicos\"])\n",
    "\n",
    "\n",
    "@tool\n",
    "def get_hotel_recommendations(location: Literal[\"aruba\", \"turks and caicos\"]):\n",
    "    \"\"\"Get hotel recommendations for a given destination.\"\"\"\n",
    "    return {\n",
    "        \"aruba\": [\n",
    "            \"The Ritz-Carlton, Aruba (Palm Beach)\"\n",
    "            \"Bucuti & Tara Beach Resort (Eagle Beach)\"\n",
    "        ],\n",
    "        \"turks and caicos\": [\"Grace Bay Club\", \"COMO Parrot Cay\"],\n",
    "    }[location]\n",
    "\n",
    "\n",
    "@tool(return_direct=True)\n",
    "def transfer_to_hotel_advisor():\n",
    "    \"\"\"Ask hotel advisor agent for help.\"\"\"\n",
    "    return \"Successfully transferred to hotel advisor\"\n",
    "\n",
    "\n",
    "@tool(return_direct=True)\n",
    "def transfer_to_travel_advisor():\n",
    "    \"\"\"Ask travel advisor agent for help.\"\"\"\n",
    "    return \"Successfully transferred to travel advisor\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d8519a32-d23b-48b0-bd18-74f8c0dacf58",
   "metadata": {},
   "source": [
    "!!! note \"Transfer tools\"\n",
    "\n",
    "    You might have noticed that we're using `@tool(return_direct=True)` in the transfer tools. This is done so that individual agents (e.g., `travel_advisor`) can exit the ReAct loop early once these tools are called. This is the desired behavior, as we want to detect when the agent calls this tool and hand control off _immediately_ to a different agent. \n",
    "    \n",
    "    **NOTE**: This is meant to work with the prebuilt [`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent] -- if you are building a custom agent, make sure to manually add logic for handling early exit for tools that are marked with `return_direct`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "93dbc3bd-27b9-4d79-b5dd-be592bc50f74",
   "metadata": {},
   "source": [
    "Now let's define our agent tasks and combine them into a single multi-agent network workflow:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "b638d6c4-3de6-4921-980c-2df1bd1cc9c7",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.messages import AIMessage\n",
    "from langchain_anthropic import ChatAnthropic\n",
    "from langgraph.prebuilt import create_react_agent\n",
    "from langgraph.graph import add_messages\n",
    "from langgraph.func import entrypoint, task\n",
    "\n",
    "model = ChatAnthropic(model=\"claude-3-5-sonnet-latest\")\n",
    "\n",
    "# Define travel advisor ReAct agent\n",
    "travel_advisor_tools = [\n",
    "    get_travel_recommendations,\n",
    "    transfer_to_hotel_advisor,\n",
    "]\n",
    "travel_advisor = create_react_agent(\n",
    "    model,\n",
    "    travel_advisor_tools,\n",
    "    prompt=(\n",
    "        \"You are a general travel expert that can recommend travel destinations (e.g. countries, cities, etc). \"\n",
    "        \"If you need hotel recommendations, ask 'hotel_advisor' for help. \"\n",
    "        \"You MUST include human-readable response before transferring to another agent.\"\n",
    "    ),\n",
    ")\n",
    "\n",
    "\n",
    "@task\n",
    "def call_travel_advisor(messages):\n",
    "    # You can also add additional logic like changing the input to the agent / output from the agent, etc.\n",
    "    # NOTE: we're invoking the ReAct agent with the full history of messages in the state\n",
    "    response = travel_advisor.invoke({\"messages\": messages})\n",
    "    return response[\"messages\"]\n",
    "\n",
    "\n",
    "# Define hotel advisor ReAct agent\n",
    "hotel_advisor_tools = [get_hotel_recommendations, transfer_to_travel_advisor]\n",
    "hotel_advisor = create_react_agent(\n",
    "    model,\n",
    "    hotel_advisor_tools,\n",
    "    prompt=(\n",
    "        \"You are a hotel expert that can provide hotel recommendations for a given destination. \"\n",
    "        \"If you need help picking travel destinations, ask 'travel_advisor' for help.\"\n",
    "        \"You MUST include human-readable response before transferring to another agent.\"\n",
    "    ),\n",
    ")\n",
    "\n",
    "\n",
    "@task\n",
    "def call_hotel_advisor(messages):\n",
    "    response = hotel_advisor.invoke({\"messages\": messages})\n",
    "    return response[\"messages\"]\n",
    "\n",
    "\n",
    "@entrypoint()\n",
    "def workflow(messages):\n",
    "    messages = add_messages([], messages)\n",
    "\n",
    "    call_active_agent = call_travel_advisor\n",
    "    while True:\n",
    "        agent_messages = call_active_agent(messages).result()\n",
    "        messages = add_messages(messages, agent_messages)\n",
    "        ai_msg = next(m for m in reversed(agent_messages) if isinstance(m, AIMessage))\n",
    "        if not ai_msg.tool_calls:\n",
    "            break\n",
    "\n",
    "        tool_call = ai_msg.tool_calls[-1]\n",
    "        if tool_call[\"name\"] == \"transfer_to_travel_advisor\":\n",
    "            call_active_agent = call_travel_advisor\n",
    "        elif tool_call[\"name\"] == \"transfer_to_hotel_advisor\":\n",
    "            call_active_agent = call_hotel_advisor\n",
    "        else:\n",
    "            raise ValueError(f\"Expected transfer tool, got '{tool_call['name']}'\")\n",
    "\n",
    "    return messages"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9223db83-1938-434a-9d24-8666842a8eea",
   "metadata": {},
   "source": [
    "Lastly, let's define a helper to render the agent outputs:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "058f3d96-534f-4b97-afb3-799ba81224ea",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.messages import convert_to_messages\n",
    "\n",
    "\n",
    "def pretty_print_messages(update):\n",
    "    if isinstance(update, tuple):\n",
    "        ns, update = update\n",
    "        # skip parent graph updates in the printouts\n",
    "        if len(ns) == 0:\n",
    "            return\n",
    "\n",
    "        graph_id = ns[-1].split(\":\")[0]\n",
    "        print(f\"Update from subgraph {graph_id}:\")\n",
    "        print(\"\\n\")\n",
    "\n",
    "    for node_name, node_update in update.items():\n",
    "        print(f\"Update from node {node_name}:\")\n",
    "        print(\"\\n\")\n",
    "\n",
    "        for m in convert_to_messages(node_update[\"messages\"]):\n",
    "            m.pretty_print()\n",
    "        print(\"\\n\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7132e2c0-d937-4325-a30e-e715c5304fe0",
   "metadata": {},
   "source": [
    "Let's test it out using the same input as our original multi-agent system:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "29b47c57-ad05-4f10-83bf-c3ff6ff8eb93",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Update from subgraph call_travel_advisor:\n",
      "\n",
      "\n",
      "Update from node agent:\n",
      "\n",
      "\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "[{'text': \"I'll help you find a warm Caribbean destination and then get some hotel recommendations for you.\\n\\nLet me first get some destination recommendations for the Caribbean region.\", 'type': 'text'}, {'id': 'toolu_015vT8PkPq1VXvjrDvSpWUwJ', 'input': {}, 'name': 'get_travel_recommendations', 'type': 'tool_use'}]\n",
      "Tool Calls:\n",
      "  get_travel_recommendations (toolu_015vT8PkPq1VXvjrDvSpWUwJ)\n",
      " Call ID: toolu_015vT8PkPq1VXvjrDvSpWUwJ\n",
      "  Args:\n",
      "\n",
      "\n",
      "Update from subgraph call_travel_advisor:\n",
      "\n",
      "\n",
      "Update from node tools:\n",
      "\n",
      "\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: get_travel_recommendations\n",
      "\n",
      "turks and caicos\n",
      "\n",
      "\n",
      "Update from subgraph call_travel_advisor:\n",
      "\n",
      "\n",
      "Update from node agent:\n",
      "\n",
      "\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "[{'text': \"Based on the recommendation, I suggest Turks and Caicos! This beautiful British Overseas Territory is known for its stunning white-sand beaches, crystal-clear turquoise waters, and year-round warm weather. Grace Bay Beach in Providenciales is consistently ranked among the world's best beaches. The islands offer excellent snorkeling, diving, and water sports opportunities, plus a relaxed Caribbean atmosphere.\\n\\nNow, let me connect you with our hotel advisor to get some specific hotel recommendations for Turks and Caicos.\", 'type': 'text'}, {'id': 'toolu_01JY7pNNWFuaWoe9ymxFYiPV', 'input': {}, 'name': 'transfer_to_hotel_advisor', 'type': 'tool_use'}]\n",
      "Tool Calls:\n",
      "  transfer_to_hotel_advisor (toolu_01JY7pNNWFuaWoe9ymxFYiPV)\n",
      " Call ID: toolu_01JY7pNNWFuaWoe9ymxFYiPV\n",
      "  Args:\n",
      "\n",
      "\n",
      "Update from subgraph call_travel_advisor:\n",
      "\n",
      "\n",
      "Update from node tools:\n",
      "\n",
      "\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: transfer_to_hotel_advisor\n",
      "\n",
      "Successfully transferred to hotel advisor\n",
      "\n",
      "\n",
      "Update from subgraph call_hotel_advisor:\n",
      "\n",
      "\n",
      "Update from node agent:\n",
      "\n",
      "\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "[{'text': 'Let me get some hotel recommendations for Turks and Caicos:', 'type': 'text'}, {'id': 'toolu_0129ELa7jFocn16bowaGNapg', 'input': {'location': 'turks and caicos'}, 'name': 'get_hotel_recommendations', 'type': 'tool_use'}]\n",
      "Tool Calls:\n",
      "  get_hotel_recommendations (toolu_0129ELa7jFocn16bowaGNapg)\n",
      " Call ID: toolu_0129ELa7jFocn16bowaGNapg\n",
      "  Args:\n",
      "    location: turks and caicos\n",
      "\n",
      "\n",
      "Update from subgraph call_hotel_advisor:\n",
      "\n",
      "\n",
      "Update from node tools:\n",
      "\n",
      "\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: get_hotel_recommendations\n",
      "\n",
      "[\"Grace Bay Club\", \"COMO Parrot Cay\"]\n",
      "\n",
      "\n",
      "Update from subgraph call_hotel_advisor:\n",
      "\n",
      "\n",
      "Update from node agent:\n",
      "\n",
      "\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "Here are two excellent hotel options in Turks and Caicos:\n",
      "\n",
      "1. Grace Bay Club: This luxury resort is located on the world-famous Grace Bay Beach. It offers all-oceanfront suites, exceptional dining options, and personalized service. The resort features adult-only and family-friendly sections, making it perfect for any type of traveler.\n",
      "\n",
      "2. COMO Parrot Cay: This exclusive private island resort offers the ultimate luxury escape. It's known for its pristine beach, world-class spa, and holistic wellness programs. The resort provides an intimate, secluded experience with top-notch amenities and service.\n",
      "\n",
      "Would you like more specific information about either of these properties or would you like to explore hotels in another destination?\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "for chunk in workflow.stream(\n",
    "    [\n",
    "        {\n",
    "            \"role\": \"user\",\n",
    "            \"content\": \"i wanna go somewhere warm in the caribbean. pick one destination and give me hotel recommendations\",\n",
    "        }\n",
    "    ],\n",
    "    subgraphs=True,\n",
    "):\n",
    "    pretty_print_messages(chunk)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d7d89ee0-0229-4718-9b98-bdd3f59c1014",
   "metadata": {},
   "source": [
    "Voila - `travel_advisor` picks a destination and then makes a decision to call `hotel_advisor` for more info!"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
